﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Samurai.WorkflowGraph.ShapeExtensions;
using Telerik.Windows.Controls;
using Telerik.Windows.Diagrams.Core;

namespace Samurai.WorkflowGraph.ViewModels
{
    /// <summary>
    /// Single-root tree layout algorithm.
    /// </summary>
    public class WorkflowLayoutAlgorithm : ViewModelBase
    {
        private readonly Dictionary<string, Size> _sizeCache = new Dictionary<string, Size>();
        private double _horizontalLevelSeparation = 20.0;
        private double _verticalLevelSeparation = 25.0;

        /// <summary>
        /// Gets or sets the horizontal pixel separation among shapes.
        /// </summary>
        /// <value>The horizontal level separation.</value>
        public double HorizontalLevelSeparation
        {
            get { return _horizontalLevelSeparation; }
            set
            {
                if (_horizontalLevelSeparation != value)
                {
                    _horizontalLevelSeparation = value;
                    OnPropertyChanged("HorizontalLevelSeparation");
                }
            }
        }

        /// <summary>
        /// Gets or sets the vertical pixel separation among shapes.
        /// </summary>
        /// <value>The vertical level separation.</value>
        public double VerticalLevelSeparation
        {
            get { return _verticalLevelSeparation; }
            set
            {
                if (_verticalLevelSeparation != value)
                {
                    _verticalLevelSeparation = value;
                    OnPropertyChanged("VerticalLevelSeparation");
                }
            }
        }

        /// <summary>
        /// Layouts all the shapes of a diagram in organizational manner.
        /// </summary>
        /// <param name="diagram">The diagram.</param>
        public void Layout(RadDiagram diagram)
        {
            if (diagram == null)
            {
                return;
            }

            var root =
                diagram.Shapes.FirstOrDefault(shape => !(shape as RadDiagramShape).HasParents()) as RadDiagramShape;

            if (root != null)
            {
                _sizeCache.Clear();
                CalculateBoundingBoxes(root);
                LayoutShape(root, new Point(0, 0));
            }
        }

        private static IEnumerable<RadDiagramShape> GetChildren(RadDiagramShape node)
        {
            return node.OutgoingLinks.Select(child => child.Target as RadDiagramShape);
        }

        private Size CalculateBoundingBoxes(RadDiagramShape node)
        {
            if (!node.HasChildren())
            {
                var size = new Size(node.ActualBounds.Width, node.ActualBounds.Height);
                _sizeCache.Add(node.Id, size);
                return size;
            }

            double width = 0;
            double height = 0;

            Size shapeBox;

            IEnumerable<RadDiagramShape> children = GetChildren(node);

            foreach (RadDiagramShape child in children)
            {
                shapeBox = CalculateBoundingBoxes(child);
                width += shapeBox.Width;
                height = Math.Max(height, shapeBox.Height);
            }

            width += (children.Count() - 1)*HorizontalLevelSeparation;
            height += HorizontalLevelSeparation + node.ActualBounds.Height;

            shapeBox = new Size(width, height);

            _sizeCache.Add(node.Id, shapeBox);

            return shapeBox;
        }

        private void LayoutShape(RadDiagramShape node, Point point)
        {
            if (!node.HasChildren())
            {
                node.Position = new Point(point.X, point.Y);
            }
            else
            {
                double x, y;
                Point selfLocation;
                Size boundingBox = _sizeCache[node.Id];
                selfLocation = new Point(point.X + ((boundingBox.Width - node.ActualBounds.Width)/2), point.Y);
                node.Position = selfLocation;

                if (Math.Abs(selfLocation.X - point.X) < Utils.Epsilon)
                {
                    x = point.X + ((node.ActualBounds.Width - boundingBox.Width)/2);
                }
                else
                {
                    x = point.X;
                }

                IEnumerable<RadDiagramShape> children = GetChildren(node);

                foreach (RadDiagramShape childNode in children)
                {
                    y = selfLocation.Y + VerticalLevelSeparation + node.ActualBounds.Height;
                    var childPoint = new Point(x, y);
                    LayoutShape(childNode, childPoint);
                    Size size = _sizeCache[childNode.Id];
                    x += size.Width + HorizontalLevelSeparation;
                }
            }
        }
    }
}